﻿/*
 * Copyright (c) Jason Paul Morley, 2010
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 */

using System;
using System.Collections.Generic;
using System.Threading;

namespace Core
{
    /// <summary>
    /// An event map that binds events to functions
    /// </summary>
    public class EventMap
    {
        public static readonly bool ConsumeEvent = true;
        public static readonly bool ContinueEvent = false;

        public interface ICallWrapper { bool Call(IEvent o); object GetFunction(); }
        public class CallWrapper<EventType> : ICallWrapper
        {
            public delegate bool Function(EventType e);
            private Function _pv_func;

            public CallWrapper(Function f) { _pv_func = f; }
            public object GetFunction() { return _pv_func; }
            public bool Call(IEvent o) { return _pv_func((EventType)o); }
        }

        /// <summary>
        /// Bind a function to an event by name
        /// </summary>
        /// <typeparam name="E"></typeparam>
        /// <param name="f"></param>
        /// <param name="name"></param>
        public void Bind<E>(CallWrapper<E>.Function f, string name)
        {
            Bind<E>(f, name.GetHashCode());
        }

        /// <summary>
        /// Bind a function to an event by hash
        /// </summary>
        /// <typeparam name="E"></typeparam>
        /// <param name="f"></param>
        /// <param name="hash"></param>
        public void Bind<E>(CallWrapper<E>.Function f, int hash)
        {
            lock (_pv_eventHashMap)
            {
                if (!_pv_eventHashMap.ContainsKey(hash))
                    _pv_eventHashMap.Add(hash, new LinkedList<ICallWrapper>());
                _pv_eventHashMap[hash].AddLast(new CallWrapper<E>(f));
            }
        }

        /// <summary>
        /// Remove a single instance of a function from an event by name
        /// </summary>
        /// <typeparam name="E"></typeparam>
        /// <param name="f"></param>
        /// <param name="name"></param>
        public void Remove<E>(CallWrapper<E>.Function f, string name) { Remove<E>(f, name.GetHashCode()); }

        /// <summary>
        /// Remove a single instance of a function from an event by hash
        /// </summary>
        /// <typeparam name="E"></typeparam>
        /// <param name="f"></param>
        /// <param name="hash"></param>
        public void Remove<E>(CallWrapper<E>.Function f, int hash)
        {
            lock (_pv_eventHashMap)
            {
                if (!_pv_eventHashMap.ContainsKey(hash)) return;
                LinkedList<ICallWrapper> l = _pv_eventHashMap[hash];
                LinkedListNode<ICallWrapper> n = l.First;

                while (n != null)
                {
                    if ((CallWrapper<E>.Function)n.Value.GetFunction() == f)
                    {
                        l.Remove(n);
                        return;
                    }
                    n = n.Next;
                }
            }
        }

        /// <summary>
        /// Remove all instances of a function from an event by name
        /// </summary>
        /// <typeparam name="E"></typeparam>
        /// <param name="f"></param>
        /// <param name="name"></param>
        public void RemoveAll<E>(CallWrapper<E>.Function f, string name) { RemoveAll<E>(f, name.GetHashCode()); }

        /// <summary>
        /// Remove all instances of a function from an event by hash
        /// </summary>
        /// <typeparam name="E"></typeparam>
        /// <param name="f"></param>
        /// <param name="hash"></param>
        public void RemoveAll<E>(CallWrapper<E>.Function f, int hash)
        {
            lock (_pv_eventHashMap)
            {
                if (!_pv_eventHashMap.ContainsKey(hash)) return;
                LinkedList<ICallWrapper> l = _pv_eventHashMap[hash];
                LinkedListNode<ICallWrapper> n = l.First;

                while (n != null)
                {
                    if ((CallWrapper<E>.Function)n.Value.GetFunction() == f) l.Remove(n);
                    n = n.Next;
                }
            }
        }

        /// <summary>
        /// Remove all functions from an event by name
        /// </summary>
        /// <param name="name"></param>
        public void RemoveAll(string name) { RemoveAll(name.GetHashCode()); }

        /// <summary>
        /// Remove all functions from an event by hash
        /// </summary>
        /// <param name="hash"></param>
        public void RemoveAll(int hash)
        {
            lock (_pv_eventHashMap)
            {
                if (!_pv_eventHashMap.ContainsKey(hash)) return;
                _pv_eventHashMap.Remove(hash);
            }
        }

        /// <summary>
        /// Trigger an event, calling it instantly
        /// </summary>
        /// <param name="e"></param>
        /// <returns>The amount of function calls made</returns>
        public int Trigger(IEvent e)
        {
            int r = 0;
            lock (_pv_eventHashMap)
            {
                r = Fire(e);
            }
            return r;
        }

        protected int Fire(IEvent e)
        {
            int r = 0;
            if (!_pv_eventHashMap.ContainsKey(e.Hash())) return r;
            LinkedList<ICallWrapper> l = _pv_eventHashMap[e.Hash()];
            foreach (ICallWrapper c in l)
            {
                ++r;
                if (c.Call(e)) return r;
            }
            return r;
        }

        /// <summary>
        /// Queue an event to be called when the queue is flushed
        /// </summary>
        /// <param name="e"></param>
        public void Queue(IEvent e)
        {
            lock (_pv_queuedEvents)
            {
                _pv_queuedEvents.AddLast(e);
            }
        }

        /// <summary>
        /// Flush the event queue, triggering all events
        /// </summary>
        /// <returns>The amount of function calls made</returns>
        public int Flush()
        {
            int r = 0;
            lock (_pv_queuedEvents)
            {
                lock (_pv_eventHashMap)
                {
                    foreach (IEvent e in _pv_queuedEvents)
                        r += Fire(e);
                }
                _pv_queuedEvents.Clear();
            }
            return r;
        }


        private LinkedList<IEvent> _pv_queuedEvents = new LinkedList<IEvent>();
        private Dictionary<int, LinkedList<ICallWrapper>> _pv_eventHashMap = new Dictionary<int,LinkedList<ICallWrapper>>();
    }
}